import os
import sys
import re
import imp
import inspect
import types
import wx
import wx.lib.newevent
import firebrick
from PreferencesPanel import PreferencesPanel

COUNT = 0
class Plugin(object):
    _id = ""
    name = ""
    required = True
    url = None
    version = ""

    def __init__(self, _id, name, version="", required=False, 
                 url=None, default_settings=None):

        if _id is None or len(_id) == 0:
            global COUNT
            _id = "plugin %s" % COUNT
            COUNT += 1
            
        self.app = wx.GetApp()
        self._id = _id
        self.name = name
        self.required = required
        self.url = url
        self.version = version
        self._enabled = False # has it been enabled?
        self._config_panel = None
        self._plugin_panel = None
        self.settings = firebrick.app.GetSettings(self._id, default_settings)

    def OnExit(self):
        '''Called when the application is exiting

        Override if your plugin needs to stop some services
        when the application exits
        '''
        pass

class PluginManager(object):
    '''Finds and loads plugins'''
    def __init__(self, app):
        self._status = {}
        self.plugin_classes = {}
        self.app = app
        self.app.Register(PluginPreferencesPanel)

    def LoadPlugins(self, pluginpath):
        plugins = []
        for path in pluginpath:
            if os.path.exists(path):
                plugins.extend(self.LoadPluginDirectory(path))

            elif re.search(r'^[a-zA-Z][.a-zA-Z0-9_]*$', path):
                # the module is not an existing file or directory, and
                # it looks like it might be a modlue name, so lets see
                # if we can load it
                try:
                    # N.B. __import__ returns the package (everything up
                    # to the last dot) rather than the module. We have
                    # to look up the module in sys.modules. Weird.
                    package = __import__(path)
                    module = sys.modules[path]
                    plugins.extend(self.LoadPluginModule(module))
                except ImportError:
                    self.log.info("unable to load plugin module '%s': %s" % (path, str(e)))
        self.plugin_classes = plugins            
        return self.plugin_classes

    def LoadPluginModule(self, module):
        '''Load all plugin classes in a module'''
        plugins = []
        self.app.log.debug("LoadPluginModule: loading %s" % module.__name__)
        for child in [_obj  for _, _obj in
                          inspect.getmembers(module, predicate=inspect.ismodule) ]:
            if (child.__package__ == module.__package__ and 
                child.__name__.endswith("Plugin")):
                plugins.extend(self.LoadPluginModule(child))

        for _obj in [ _obj for _, _obj in
                        inspect.getmembers(module, predicate=inspect.isclass) ]:
            try:
                is_plugin = issubclass(_obj, Plugin) and _obj is not Plugin
            except TypeError:
                # this will happen if _obj isn't
                # actually a class, which can happen with
                # the inspect module in python versions
                # below 2.7. If this happens, all that
                # means is that what we have in our hand
                # is not a plugin.
                is_plugin = False
            if is_plugin:
                self.app.log.debug("... found plugin class '%s'" % _obj.__name__)
                plugins.append(_obj)
                self._status[module.__name__] = ("loaded",)
        return plugins

    def LoadPluginDirectory(self, dirname):
        '''Loads (but doesn't enable) all plugins it can find 

        This will recurse into any nested directories.
        '''
        plugins = []
        self.app.log.debug("LoadPlugins: looking in '%s'" % dirname)
        for filename in os.listdir(dirname):
            path = os.path.join(dirname, filename)
            if os.path.isdir(path):
                plugins.extend(self.LoadPluginDirectory(path))
            else:
                if not self._is_plugin_file(filename): continue
                plugins.extend(self.LoadPluginFile(path))
        return plugins

    def LoadPluginFile(self, path):
        '''Loads the given plugin file; returns list of plugin classes'''
#        if not self._is_plugin_file(path): 
#            return []

        modulename = os.path.splitext(path)[0]
        plugins = []
        try:
            self.app.log.debug("... attempting to load '%s'" % path)
            module = imp.load_source(modulename, path)
            return self.LoadPluginModule(module)
        except Exception, e:
            self.app.log.error("error while loading plugin:", exc_info=e)
            self._status[modulename] = ("error",e)
        return []

    def EnablePlugins(self):
        '''Instantiate and enable all loaded plugins'''
        # This should be getting the enabled/disabled state from
        # persistent settings. For now, enable all plugins
        self.plugins = []
        for plugin_class in self.plugin_classes:
            plugin = plugin_class(self.app)
            self.app.log.debug("enabling plugin '%s'" % plugin._id)
            try:
                plugin.enable()
                self.plugins.append(plugin)
            except Exception, e:
                self.app.log.error("EnablePlugins: error while enabling '%s':" % 
                                   plugin._id, exc_info = e)

    def OnExit(self):
        '''Notify all plugins the app is exiting'''
        for plugin in self.plugins:
            plugin.OnExit()

    def _is_plugin_file(self, filename):
        '''Return true if the filename looks like a plugin file'''
        return filename.endswith("Plugin.py") and \
            not filename.startswith(".") and \
            not filename.startswith("_")

class PluginPreferencesPanel(PreferencesPanel):
    location = ("Plugins",)

    def __init__(self, parent, *args, **kwargs):
        firebrick.PreferencesPanel.__init__(self, parent, *args, **kwargs)
        sizer = wx.BoxSizer(wx.VERTICAL)
        for plugin in firebrick.app.GetPlugins():
            label = "%s %s" % (plugin.name, plugin.version)
            text = wx.StaticText(self, label=label)
            sizer.Add(text, 0)
        self.SetSizer(sizer)

